<template>
    <el-checkbox-group class="tnxel-select tnxel-checkbox-group" v-model="model" :theme="theme" :size="size"
        :disabled="disabled" :key="groupKey" v-if="selector === 'checkbox'">
        <template v-if="items">
            <el-checkbox v-for="item in items" :key="item[valueName]" :value="item[valueName]"
                :data-value="item[valueName]"
                :disabled="item.disabled === true || typeof item.disabled === 'string'"
                :title="typeof item.disabled === 'string' ? item.disabled : item.title">
                <slot name="option" :item="item" v-if="$slots.option"></slot>
                <template v-else>
                    <i :class="item[iconName]" v-if="item[iconName]"></i>
                    <span>{{ item[textName] }}</span>
                </template>
            </el-checkbox>
            <template v-if="items.length === 0">
                <slot name="empty" v-if="$slots.empty"></slot>
                <span class="text-secondary" :class="emptyClass" v-else-if="emptyText">{{ emptyText }}</span>
            </template>
        </template>
        <tnxel-icon value="Loading" v-else/>
    </el-checkbox-group>
    <div class="tnxel-select tnxel-tag-group d-flex flex-wrap" :key="groupKey"
        v-else-if="selector === 'tag' || selector === 'tags'">
        <template v-if="items">
            <el-button link :size="size" :class="emptyClass" v-if="emptyText" @click="clear">
                {{ emptyText }}
            </el-button>
            <el-tag v-for="item in items" :key="item[valueName]" :type="theme"
                :effect="isSelected(item[valueName]) ? 'dark' : 'plain'" :data-value="item[valueName]"
                :disabled="item.disabled === true || typeof item.disabled === 'string'"
                :title="typeof item.disabled === 'string' ? item.disabled : item.title"
                @click="select(item[valueName])">
                <slot name="option" :item="item" v-if="$slots.option"></slot>
                <template v-else>
                    <i :class="item[iconName]" v-if="item[iconName]"></i>
                    <span>{{ item[textName] }}</span>
                </template>
            </el-tag>
            <template v-if="items.length === 0">
                <slot name="empty"></slot>
            </template>
        </template>
        <tnxel-icon value="Loading" v-else/>
    </div>
    <div class="tnxel-select tnxel-text-button-group d-flex flex-wrap" :key="groupKey"
        v-else-if="selector === 'text' || selector === 'texts'">
        <template v-if="items">
            <el-button link :size="size" :class="emptyClass" v-if="emptyText" @click="clear">
                {{ emptyText }}
            </el-button>
            <el-button v-for="item in items" :key="item[valueName]"
                :link="!isSelected(item[valueName])" :plain="isSelected(item[valueName])"
                :size="size" :data-value="item[valueName]"
                :disabled="item.disabled === true || typeof item.disabled === 'string'"
                :title="typeof item.disabled === 'string' ? item.disabled : item.title"
                @click="select(item[valueName], $event)">
                <slot name="option" :item="item" v-if="$slots.option"></slot>
                <template v-else>
                    <i :class="item[iconName]" v-if="item[iconName]"></i>
                    <span>{{ item[textName] }}</span>
                </template>
            </el-button>
            <template v-if="items.length === 0">
                <slot name="empty"></slot>
            </template>
        </template>
        <tnxel-icon value="Loading" v-else/>
    </div>
    <el-radio-group class="tnxel-select tnxel-radio-group ignore-feedback" v-model="model" :theme="theme" :size="size"
        :disabled="disabled" :key="groupKey" v-else-if="selector === 'radio'">
        <el-radio :value="emptyValue" :class="emptyClass" :border="border" v-if="empty">{{ emptyText }}</el-radio>
        <el-radio v-for="item in items" :key="item[valueName]" :value="item[valueName]" :data-value="item[valueName]"
            :disabled="item.disabled === true || typeof item.disabled === 'string'"
            :title="typeof item.disabled === 'string' ? item.disabled : item.title"
            :border="border">
            <slot name="option" :item="item" v-if="$slots.option"></slot>
            <template v-else>
                <i :class="item[iconName]" v-if="item[iconName]"></i>
                <span>{{ item[textName] }}</span>
            </template>
        </el-radio>
    </el-radio-group>
    <el-radio-group class="tnxel-select tnxel-radio-group ignore-feedback" v-model="model" :theme="theme" :size="size"
        :disabled="disabled" :key="groupKey" v-else-if="selector === 'radio-button'">
        <el-radio-button :class="emptyClass" :value="emptyValue" v-if="empty">{{ emptyText }}</el-radio-button>
        <el-radio-button v-for="item in items" :key="item[valueName]" :value="item[valueName]"
            :data-value="item[valueName]"
            :disabled="item.disabled === true || typeof item.disabled === 'string'"
            :title="typeof item.disabled === 'string' ? item.disabled : item.title">
            <slot name="option" :item="item" v-if="$slots.option"></slot>
            <template v-else>
                <i :class="item[iconName]" v-if="item[iconName]"></i>
                <span>{{ item[textName] }}</span>
            </template>
        </el-radio-button>
    </el-radio-group>
    <el-dropdown class="tnxel-select tnxel-dropdown"
        popper-class="tnxel-select-dropdown-popper"
        trigger="click"
        :key="groupKey"
        :size="size"
        @command="onDropdownCommand"
        v-else-if="selector === 'dropdown'">
        <el-button style="max-width: 100%" :type="theme" :title="currentText">
            <span class="flex-1 overflow-ellipsis me-2">{{ currentText }}</span>
            <tnxel-icon value="ArrowDown"/>
        </el-button>
        <template #dropdown v-if="items && items.length">
            <el-dropdown-menu>
                <el-dropdown-item v-for="item in items" :key="item[valueName]"
                    :command="item[valueName]"
                    :data-value="item[valueName]"
                    :class="{'selected': isSelected(item[valueName])}"
                    :disabled="item.disabled === true || typeof item.disabled === 'string'"
                    :title="typeof item.disabled === 'string' ? item.disabled : item.title"
                >
                    <slot name="option" :item="item" v-if="$slots.option"></slot>
                    <template v-else>
                        <i :class="item[iconName]" v-if="item[iconName]"></i>
                        <span>{{ item[textName] }}</span>
                    </template>
                </el-dropdown-item>
            </el-dropdown-menu>
        </template>
    </el-dropdown>
    <el-dropdown class="tnxel-select tnxel-split-dropdown"
        popper-class="tnxel-select-dropdown-popper"
        trigger="click"
        :key="groupKey"
        :type="theme"
        :size="size"
        split-button
        @command="onDropdownCommand"
        v-else-if="selector === 'split-dropdown'">
        <span>{{ currentText }}</span>
        <template #dropdown v-if="items && items.length">
            <el-dropdown-menu>
                <el-dropdown-item v-for="item in items" :key="item[valueName]"
                    :command="item[valueName]"
                    :data-value="item[valueName]"
                    :class="{'selected': isSelected(item[valueName])}"
                    :disabled="item.disabled === true || typeof item.disabled === 'string'"
                    :title="typeof item.disabled === 'string' ? item.disabled : item.title"
                >
                    <slot name="option" :item="item" v-if="$slots.option"></slot>
                    <template v-else>
                        <i :class="item[iconName]" v-if="item[iconName]"></i>
                        <span>{{ item[textName] }}</span>
                    </template>
                </el-dropdown-item>
            </el-dropdown-menu>
        </template>
    </el-dropdown>
    <el-select class="tnxel-select ignore-feedback" v-model="model" :placeholder="placeholder" :theme="theme"
        :size="size" :disabled="disabled" :filterable="filterable" :filter-method="filter"
        :multiple="selector === 'multi-select'" :key="groupKey" v-else>
        <el-option class="text-secondary" :value="emptyValue" :class="emptyClass" v-if="empty">
            {{ emptyText }}
        </el-option>
        <template v-for="item in items">
            <el-option :key="item[valueName]" :value="item[valueName]" :label="item[textName]"
                :data-value="item[valueName]" :disabled="item.disabled === true || typeof item.disabled === 'string'"
                :title="typeof item.disabled === 'string' ? item.disabled : item.title"
                v-if="!hiddenValues.contains(item[valueName])">
                <slot name="option" :item="item" v-if="$slots.option"></slot>
                <span v-else>{{ item[textName] }}</span>
            </el-option>
        </template>
    </el-select>
</template>

<script>
import Icon from '../icon/Icon.vue';

export const isMultiSelector = function (selector) {
    return selector === 'checkbox' || selector === 'tags' || selector === 'multi-select' || selector === 'texts';
}
export default {
    name: 'TnxelSelect',
    components: {
        'tnxel-icon': Icon,
    },
    props: {
        id: [Number, String],
        modelValue: {
            type: [String, Number, Boolean, Array],
            default: null,
        },
        selector: String,
        items: Array,
        valueName: {
            type: String,
            default: 'value',
        },
        textName: {
            type: String,
            default: 'text',
        },
        indexName: {
            type: String,
            default: 'index',
        },
        iconName: {
            type: String,
            default: 'icon',
        },
        defaultValue: {
            type: [String, Number, Boolean, Array],
            default: null,
        },
        empty: {
            type: [Boolean, String],
            default: false,
        },
        emptyValue: {
            type: [String, Number, Boolean, Array],
            default: '', // 设为null会告警，暂时先设为空字符，在非字符串类型的情况下是否有问题，有待检验
        },
        emptyClass: String,
        placeholder: String,
        disabled: Boolean,
        tagClick: Function, // 点击一个标签选项时调用，如果返回false，则选项不会被选中
        change: Function, // 选中值变化后的事件处理函数，比change事件传递更多参数数据
        filterable: Boolean,
        theme: String,
        size: String,
        border: Boolean,
    },
    emits: ['update:modelValue', 'change'],
    data() {
        let model = this.getModel(this.items);
        if (this.items && model !== this.modelValue) {
            this.$emit('update:modelValue', model);
        }
        return {
            groupKey: new Date().getTime(),
            model: model,
            hiddenValues: [],
        };
    },
    computed: {
        emptyText() {
            return typeof this.empty === 'string' ? this.empty : '';
        },
        currentText() {
            let item = this.getItem(this.model);
            return item ? item[this.textName] : this.emptyText;
        },
        modelEqualsModelValue() {
            if (Array.isArray(this.model) && Array.isArray(this.modelValue)) {
                return this.model.equals(this.modelValue);
            } else {
                return this.model === this.modelValue;
            }
        },
        firstEnabledItem() {
            for (let item of this.items) {
                if (!item.disabled) {
                    return item;
                }
            }
            return undefined;
        },
    },
    watch: {
        model(newValue, oldValue) {
            if (!this.modelEqualsModelValue) {
                this.$emit('update:modelValue', newValue);
                // 新旧值不同时为空才触发变更事件
                const util = window.tnx.util;
                if (util.object.isNotEmpty(newValue) || util.object.isNotEmpty(oldValue)) {
                    let vm = this;
                    // 确保变更事件在值变更应用后再触发
                    this.$nextTick(() => {
                        vm.triggerChange(newValue);
                    });
                }
            }
        },
        modelValue() {
            this.model = this.getModel(this.items);
        },
        items(items) {
            this.model = this.getModel(items);
        },
    },
    methods: {
        isMulti() {
            return isMultiSelector(this.selector);
        },
        triggerChange(value) {
            if (this.change) {
                let item = undefined;
                if (this.isMulti()) {
                    item = [];
                    if (Array.isArray(value)) {
                        for (let v of value) {
                            item.push(this.getItem(v));
                        }
                    }
                } else {
                    item = this.getItem(value);
                }
                this.change(item, this.id);
            } else {
                this.$emit('change', value);
            }
        },
        getItem(value) {
            if (this.empty && value === this.emptyValue) {
                let item = {};
                item[this.valueName] = this.emptyValue;
                item[this.textName] = this.emptyText;
                return item;
            }
            if (value !== undefined && value !== null && this.items) {
                for (let item of this.items) {
                    if ((item[this.valueName] + '') === (value + '')) {
                        return item;
                    }
                }
            }
            return undefined;
        },
        getText(value) {
            let item = this.getItem(value);
            return item ? item[this.textName] : undefined;
        },
        getModel(items) {
            const util = window.tnx.util;
            let model = this.modelValue;
            if (util.object.isNull(model)) {
                model = this.defaultValue;
            }
            if (this.isMulti()) { // 多选时需确保值为数组
                if (util.object.isNull(model)) {
                    return [];
                }
                if (!Array.isArray(model)) {
                    model = [model];
                }
                if (items && items.length) {
                    // 多选时，如果model中有值不在items中，则去掉
                    for (let i = model.length - 1; i >= 0; i--) {
                        let item = this.getItem(model[i]);
                        if (!item) {
                            model.splice(i, 1);
                        }
                    }
                } else {
                    model = [];
                }
                return model;
            }
            if (util.object.isNull(model)) {
                return null;
            }
            if (items && items.length) {
                let item = this.getItem(model);
                if (item) {
                    return item[this.valueName];
                } else { // 如果当前值找不到匹配的选项，则需要考虑是设置为空还是默认选项
                    if (!this.empty) { // 如果不能为空，则默认选中第一个可用选项
                        let firstItem = this.firstEnabledItem;
                        if (firstItem && firstItem[this.valueName]) {
                            return firstItem[this.valueName];
                        }
                    }
                }
            }
            return model;
        },
        filter(keyword) {
            for (let item of this.items) {
                let itemValue = item[this.valueName];
                let hiddenIndex = this.hiddenValues.indexOf(itemValue);
                if (this.matchesItem(item, keyword)) {
                    if (hiddenIndex >= 0) { // 匹配且原本已隐藏的，则取消隐藏
                        this.hiddenValues.splice(hiddenIndex, 1);
                    }
                } else {
                    if (hiddenIndex < 0) { // 不匹配且原本未隐藏的，则进行隐藏
                        this.hiddenValues.push(itemValue);
                    }
                }
            }
        },
        matchesItem(item, keyword) {
            return !keyword || window.tnx.util.string.matchesForEach(item[this.valueName], keyword)
                || window.tnx.util.string.matchesForEach(item[this.textName], keyword)
                || window.tnx.util.string.matchesForEach(item[this.indexName], keyword)
        },
        onDropdownCommand(value) {
            this.model = value;
        },
        isSelected(value) {
            if (Array.isArray(this.model)) {
                return this.model.contains(value);
            } else {
                return this.model === value;
            }
        },
        select(value, event) {
            if (this.tagClick) {
                let item = this.getItem(value);
                if (item) {
                    if (this.tagClick(item) === false) {
                        return;
                    }
                }
            }
            if (this.isMulti()) {
                let index = this.model.indexOf(value);
                if (index >= 0) {
                    this.model = this.model.filter((e, i) => {
                        return i !== index;
                    });
                } else {
                    this.model = this.model.concat([value]);
                }
            } else {
                this.model = value;
            }
            if (event) {
                event.currentTarget.blur();
            }
        },
        clear() {
            if (this.isMulti()) {
                this.model = [];
            } else {
                this.model = null;
            }
        },
        disableItem(itemValue, disabled, reverseOther) {
            let values = Array.isArray(itemValue) ? itemValue : [itemValue];
            for (let item of this.items) {
                if (values.contains(item[this.valueName])) {
                    item.disabled = disabled;
                    // 如果禁用的选项已被选择，则需从已选择值中移除该选项的值
                    if (this.isMulti()) {
                        if (this.model.includes(itemValue)) {
                            this.model.remove(itemValue);
                        }
                    } else {
                        if (this.model === itemValue) {
                            this.model = undefined;
                        }
                    }
                } else if (reverseOther) {
                    item.disabled = !disabled;
                }
            }
            this.groupKey = new Date().getTime();
        },
    }
}
</script>

<style>
.tnxel-tag-group .el-tag {
    margin-top: 5px;
    margin-bottom: 5px;
    cursor: pointer;
}

.tnxel-tag-group .el-tag:not(:last-child) {
    margin-right: 10px;
}

.tnxel-tag-group .el-button,
.tnxel-text-button-group .el-button {
    padding: 0.5rem 0.75rem;
    margin: 2px 0.5rem 2px 0;
}

.tnxel-tag-group .el-button__text--expand,
.tnxel-text-button-group .el-button__text--expand {
    letter-spacing: unset;
    margin-right: unset;
}

.tnxel-tag-group .el-button.is-link,
.tnxel-text-button-group .el-button.is-link {
    color: unset;
}

.tnxel-tag-group .el-button.is-link:hover,
.tnxel-text-button-group .el-button.is-link:hover {
    color: var(--el-color-primary);
    border-color: transparent;
}

.tnxel-text-button-group .el-button.is-plain {
    border-color: var(--el-color-primary);
    color: var(--el-color-primary);
}

.tnxel-text-button-group .el-button + .el-button {
    margin-left: 0;
}

.tnxel-radio-group .el-radio:not(:last-child) {
    margin-right: 20px;
}

.tnxel-radio-group[theme="error"] .el-radio-button__original-radio:checked + .el-radio-button__inner,
.tnxel-radio-group[theme="danger"] .el-radio-button__original-radio:checked + .el-radio-button__inner {
    background-color: var(--el-color-danger);
    border-color: var(--el-color-danger);
    box-shadow: -1px 0 0 0 var(--el-color-danger);
}

.tnxel-radio-group[theme="error"] .el-radio-button__original-radio:not(:checked) + .el-radio-button__inner:hover,
.tnxel-radio-group[theme="danger"] .el-radio-button__original-radio:not(:checked) + .el-radio-button__inner:hover {
    color: var(--el-color-danger);
}

.tnxel-radio-group[theme="warning"] .el-radio-button__original-radio:checked + .el-radio-button__inner {
    background-color: var(--el-color-warning);
    border-color: var(--el-color-warning);
    box-shadow: -1px 0 0 0 var(--el-color-warning);
}

.tnxel-radio-group[theme="warning"] .el-radio-button__original-radio:not(:checked) + .el-radio-button__inner:hover {
    color: var(--el-color-warning);
}

.tnxel-radio-group[theme="success"] .el-radio-button__original-radio:checked + .el-radio-button__inner {
    background-color: var(--el-color-success);
    border-color: var(--el-color-success);
    box-shadow: -1px 0 0 0 var(--el-color-success);
}

.tnxel-radio-group[theme="success"] .el-radio-button__original-radio:not(:checked) + .el-radio-button__inner:hover {
    color: var(--el-color-success);
}

.tnxel-select-dropdown-popper .el-dropdown-menu__item.selected {
    color: var(--el-color-primary);
    font-weight: 600;
}

.tnxel-dropdown .el-button > span {
    max-width: 100%;
    display: flex;
    align-items: center;
}
</style>
